/*************************************************************************
  Crytek Source File.
  Copyright (C), Crytek Studios, 2001-2004.
 -------------------------------------------------------------------------
  $Id$
  $DateTime$
  
 -------------------------------------------------------------------------
  History:
  - 17:8:2004   18:12 : Created by Marcio Martins

*************************************************************************/
#include "StdAfx.h"
#include "LevelSystem.h"
#include "Level.h"
#include "ActorSystem.h"
#include "IMusicSystem.h"
#include "IMovieSystem.h"
#include "CryAction.h"
#include "IGameTokens.h"
#include "IDialogSystem.h"
#include "TimeOfDayScheduler.h"
#include "IGameRulesSystem.h"
#include "IMaterialEffects.h"
#include "IPlayerProfiles.h"
#include <IResourceManager.h>
#include <ILocalizationManager.h>
#include "AnimationGraph/AnimationGraphManager.h"
#include "Network/GameContext.h"
#include "ICooperativeAnimationManager.h"
#include "IDeferredCollisionEvent.h"
#include "IPlatformOS.h"

#include "LoadSeq.h"

#ifdef WIN32
#include <windows.h>
#endif





#define LOCAL_WARNING(cond, msg)  do { if (!(cond)) { CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, msg); } } while(0)
//#define LOCAL_WARNING(cond, msg)  CRY_ASSERT_MESSAGE(cond, msg)

int CLevelSystem::s_loadCount = 0;

//------------------------------------------------------------------------
CLevelRotation::CLevelRotation() :
  m_random(false),
	m_next(0),
	m_extInfoId(0)
{
}

//------------------------------------------------------------------------
CLevelRotation::~CLevelRotation()
{
}

//------------------------------------------------------------------------
bool CLevelRotation::Load(ILevelRotationFile* file)
{
	return LoadFromXmlRootNode(file->Load(), NULL);
}

//------------------------------------------------------------------------
bool CLevelRotation::LoadFromXmlRootNode(const XmlNodeRef rootNode, const char* altRootTag)
{
	if (rootNode)
	{
		if (!stricmp(rootNode->getTag(), "levelrotation") || (altRootTag && !stricmp(rootNode->getTag(), altRootTag)))
		{
			Reset();

      bool r;
      if(rootNode->getAttr("randomize",r))
			{
        SetRandom(r);
			}

      bool includeNonPresentLevels = false;
      rootNode->getAttr("includeNonPresentLevels", includeNonPresentLevels);

			int n=rootNode->getChildCount();

			for (int i=0;i<n;i++)
			{
				XmlNodeRef child=rootNode->getChild(i);
				if (child && !stricmp(child->getTag(), "level"))
				{
					const char *levelName=child->getAttr("name");
					if (!levelName || !levelName[0])
					{
						continue;
					}

					if (!includeNonPresentLevels)
					{
						if (!CCryAction::GetCryAction()->GetILevelSystem()->GetLevelInfo(levelName)) //skipping non-present levels
						{
							continue;
						}
					}

					const char *gameRulesName=child->getAttr("gamerules");
					if (!gameRulesName || !gameRulesName[0])
					{
						gameRulesName=0;
					}

					int lvl = AddLevel(levelName, gameRulesName);
          for(int j=0;j<child->getChildCount();++j)
          {
            XmlNodeRef s = child->getChild(j);
            if(s && !stricmp(s->getTag(), "setting"))
            {
              const char *setting = s->getAttr("setting");
              AddSetting(lvl, setting);
            }
          }
				}
			}

      if(!m_extInfoId && IsRandom())
			{
        Shuffle(-1);
			}

			return true;
		}
	}

	return false;
}

//------------------------------------------------------------------------
bool CLevelRotation::Save(ILevelRotationFile* file)
{
	XmlNodeRef rootNode=gEnv->pSystem->CreateXmlNode("levelRotation");

  rootNode->setAttr("randomize",m_random);

	for (int i=0; i<m_rotation.size(); i++)
	{
		SLevelRotationEntry &entry=m_rotation[i];

		XmlNodeRef level=gEnv->pSystem->CreateXmlNode("level");
		level->setAttr("name", entry.levelName.c_str());
		if (!entry.gameRulesName.empty())
			level->setAttr("gameRules", entry.gameRulesName.c_str());
    
    for(int j=0;j<entry.settings.size();++j)
    {
      XmlNodeRef s=gEnv->pSystem->CreateXmlNode("setting");
      s->setAttr("setting",entry.settings[j].c_str());
      level->addChild(s);
    }

    rootNode->addChild(level);
	}

	return file->Save(rootNode);
}

//------------------------------------------------------------------------
void CLevelRotation::Reset()
{
  m_random = false;
	m_next = 0;
	m_rotation.resize(0);
  m_shuffle.resize(0);
	m_extInfoId = 0;
}

//------------------------------------------------------------------------
int CLevelRotation::AddLevel(const char *level, const char *gamerules)
{
	SLevelRotationEntry entry;
	entry.levelName=level;
	if (gamerules)
		entry.gameRulesName=gamerules;

  int idx = m_rotation.size();
	m_rotation.push_back(entry);
  if(m_random)
    m_shuffle.push_back(idx);
  return idx;
}

void CLevelRotation::AddSetting(int level, const char* setting)
{
  CRY_ASSERT(level>=0 && level<m_rotation.size());
  m_rotation[level].settings.push_back(setting);
}

//------------------------------------------------------------------------
bool CLevelRotation::First()
{
	m_next=0;
  if(!m_extInfoId && m_random)
    Shuffle(-1);
	return !m_rotation.empty();
}

//------------------------------------------------------------------------
bool CLevelRotation::Advance()
{
	++m_next;
  if (m_next>=m_rotation.size())
		return false;
	return true;
}

//------------------------------------------------------------------------
const char *CLevelRotation::GetNextLevel() const
{
  if (m_next>=0 && m_next<m_rotation.size())
  {
    int next = m_random?m_shuffle[m_next]:m_next;
		return m_rotation[next].levelName.c_str();
  }
	return 0;
}

//------------------------------------------------------------------------
const char *CLevelRotation::GetNextGameRules() const
{
	if (m_next>=0 && m_next<m_rotation.size())
  {
    int next = m_random?m_shuffle[m_next]:m_next;
    return m_rotation[next].gameRulesName.c_str();
  }
	return 0;
}

//------------------------------------------------------------------------

int  CLevelRotation::GetNextSettingsNum() const
{
  if (m_next>=0 && m_next<m_rotation.size())
  {
    int next = m_random?m_shuffle[m_next]:m_next;    
    return m_rotation[next].settings.size();
  }
  return 0;
}

//------------------------------------------------------------------------

const char *CLevelRotation::GetNextSetting(int idx)
{
  if (m_next>=0 && m_next<m_rotation.size())
  {
    int next = m_random?m_shuffle[m_next]:m_next;    
    if(idx >=0 && idx < m_rotation[next].settings.size())
      return m_rotation[next].settings[idx].c_str();
    else
      return 0;
  }
  return 0;
}

//------------------------------------------------------------------------

int CLevelRotation::GetLength() const
{
	return (int)m_rotation.size();
}

int CLevelRotation::GetNext() const
{
	return m_next;
}

//------------------------------------------------------------------------
void CLevelRotation::SetRandom(bool rnd)
{
	m_random = rnd;
	if(!m_extInfoId && m_random)
	{
		Shuffle(-1);
	}
}

//------------------------------------------------------------------------

bool CLevelRotation::IsRandom()const
{
  return m_random;
}

//------------------------------------------------------------------------
void CLevelRotation::ChangeLevel(IConsoleCmdArgs* pArgs)
{
	SGameContextParams ctx;
	const char *nextGameRules=GetNextGameRules();
	if (nextGameRules && nextGameRules[0])
		ctx.gameRules = nextGameRules;
	else if (IEntity *pEntity=CCryAction::GetCryAction()->GetIGameRulesSystem()->GetCurrentGameRulesEntity())
		ctx.gameRules = pEntity->GetClass()->GetName();
	else if (ILevelInfo *pLevelInfo=CCryAction::GetCryAction()->GetILevelSystem()->GetLevelInfo(GetNextLevel()))
		ctx.gameRules = pLevelInfo->GetDefaultGameType()->name;

	ctx.levelName = GetNextLevel();

  for(int i=0;i<GetNextSettingsNum();++i)
  {
    gEnv->pConsole->ExecuteString(GetNextSetting(i));
  }
  
  if (CCryAction::GetCryAction()->StartedGameContext())
  {
		CCryAction::GetCryAction()->ChangeGameContext(&ctx);
  }
  else
  {
    gEnv->pConsole->ExecuteString( string("sv_gamerules ") + ctx.gameRules );

    string command = string("map ") + ctx.levelName;
    if (pArgs)
      for (int i = 1; i < pArgs->GetArgCount(); ++i)
        command += string(" ") + pArgs->GetArg(i);
		command += " s";
    gEnv->pConsole->ExecuteString(command);
  }
/*  else
  {
    int flags = eGSF_ImmersiveMultiplayer;

    SGameStartParams params;
    params.flags = flags | eGSF_Server;

    if (!gEnv->IsDedicated())
    {
      params.flags |= eGSF_Client;
      params.hostname = "localhost";
    }
    
    ICVar* max_players = gEnv->pConsole->GetCVar("sv_maxplayers");
    params.maxPlayers = max_players?max_players->GetIVal():32; 
    ICVar* loading = gEnv->pConsole->GetCVar("g_enableloadingscreen");
    if(loading)
      loading->Set(0);
    
    params.recording = false;//TODO : talk to Lin

    params.pContextParams = &ctx;

    params.port = gEnv->pConsole->GetCVar("sv_port")->GetIVal();

    CCryAction::GetCryAction()->StartGameContext(&params);
  }*/
	
	if (!Advance())
		First();
}

static CRndGen g_randomGenerator;

//------------------------------------------------------------------------
void CLevelRotation::Shuffle(int nSeed)
{

  m_shuffle.resize(m_rotation.size());
  if(m_rotation.empty())
    return;

	if (nSeed >= 0)
	{
		g_randomGenerator.seed(nSeed);
	}
  for(int i=0;i<m_shuffle.size();++i)
    m_shuffle[i] = i;
  for(int i=0;i<m_shuffle.size();++i)
  {
    int idx = g_randomGenerator.Generate()%m_shuffle.size();
    std::swap(m_shuffle[i],m_shuffle[idx]);
  }

	CryLog("Shuffle seed: %d = %d, %d", nSeed, m_shuffle[0], m_shuffle[1]);

}

//------------------------------------------------------------------------
bool CLevelInfo::SupportsGameType(const char *gameTypeName) const
{
	//read level meta data
  for(int i=0;i<m_gamerules.size();++i)
  {
    if(!stricmp(m_gamerules[i].c_str(),gameTypeName))
      return true;
  }
  return false;
}

//------------------------------------------------------------------------
const char* CLevelInfo::GetDisplayName() const
{
	return m_levelDisplayName.c_str();
}

//------------------------------------------------------------------------
bool CLevelInfo::GetAttribute(const char* name, TFlowInputData& val) const
{
	TAttributeList::const_iterator it = m_levelAttributes.find(name);
	if (it != m_levelAttributes.end())
	{
		val = it->second;
		return true;
	}
	return false;
}

//////////////////////////////////////////////////////////////////////////
bool CLevelInfo::ReadInfo()
{
	string levelPath = m_levelPath;
	//string paks = levelPath + string("/*.pak");
	if (!OpenLevelPak())
		return false;









	string xmlFile = levelPath + string("/LevelInfo.xml");
	XmlNodeRef	rootNode = GetISystem()->LoadXmlFromFile(xmlFile.c_str());
	if (rootNode)
	{
		const char* sandboxVersion = rootNode->getAttr("SandboxVersion");
		if (sandboxVersion)
		{
			int major, minor, bugfix;
			int buildNumber = 0;
			if (sscanf(sandboxVersion, "%i.%i.%i.%i", &major, &minor, &bugfix, &buildNumber) == 4)
			{
				const int leakedBuildNumber = 5620;
				if (buildNumber == leakedBuildNumber)
					return false;
			}
		}

		m_heightmapSize = atoi(rootNode->getAttr("HeightmapSize"));

		string dataFile = levelPath + string("/LevelDataAction.xml");
		XmlNodeRef dataNode = GetISystem()->LoadXmlFromFile(dataFile.c_str());
		if(!dataNode)
		{
			dataFile = levelPath + string("/LevelData.xml");
			dataNode = GetISystem()->LoadXmlFromFile(dataFile.c_str());
		}

		if (dataNode)
		{
			XmlNodeRef gameTypesNode = dataNode->findChild("Missions");

			if ((gameTypesNode!=0) && (gameTypesNode->getChildCount() > 0))
			{
				// I can't be certain from looking at the code that these aren't used outside of a level, so better put them on the global heap
				ScopedSwitchToGlobalHeap useGlobalHeap;
				m_gameTypes.clear();
				m_musicLibs.clear();

				for (int i = 0; i < gameTypesNode->getChildCount(); i++)
				{
					XmlNodeRef gameTypeNode = gameTypesNode->getChild(i);

					if (gameTypeNode->isTag("Mission"))
					{
						const char *gameTypeName = gameTypeNode->getAttr("Name");

						if (gameTypeName)
						{
							ILevelInfo::TGameTypeInfo info;

							info.cgfCount = 0;
							gameTypeNode->getAttr("CGFCount", info.cgfCount);
							info.name = gameTypeNode->getAttr("Name");
							info.xmlFile = gameTypeNode->getAttr("File");
							m_gameTypes.push_back(info);
						}
					}
				}

				XmlNodeRef musicLibraryNode = dataNode->findChild("MusicLibrary");

				if ((musicLibraryNode!=0) && (musicLibraryNode->getChildCount() > 0))
				{
					for (int i = 0; i < musicLibraryNode->getChildCount(); i++)
					{
						XmlNodeRef musicLibrary = musicLibraryNode->getChild(i);

						if (musicLibrary->isTag("Library"))
						{
							const char *musicLibraryName = musicLibrary->getAttr("File");

							if (musicLibraryName)
							{
								m_musicLibs.push_back(string("music/") + musicLibraryName);
							}
						}
					}
				}
			}
		}
	}
	return rootNode != 0;
}

//////////////////////////////////////////////////////////////////////////
void CLevelInfo::ReadMetaData()
{
  string fullPath(GetPath());
  int slashPos = fullPath.find_last_of("\\/");
  string mapName = fullPath.substr(slashPos+1, fullPath.length()-slashPos);
  fullPath.append("/");
  fullPath.append(mapName);
  fullPath.append(".xml");
	m_levelAttributes.clear();

	m_levelDisplayName = "@ui_";
	m_levelDisplayName.append(mapName);

	if (!gEnv->pCryPak->IsFileExist(fullPath.c_str()))
		return;

  XmlNodeRef mapInfo = GetISystem()->LoadXmlFromFile(fullPath.c_str());
  //retrieve the coordinates of the map
	bool foundMinimapInfo = false;
  if(mapInfo)
  {
		m_gamerules.clear();

    for(int n = 0; n < mapInfo->getChildCount(); ++n)
    {
      XmlNodeRef child = mapInfo->getChild(n);
      const char* name = child->getTag();
      if(!stricmp(name, "Gamerules"))
      {
        for(int a = 0; a < child->getNumAttributes(); ++a)
        {
          const char *key, *value;
          child->getAttributeByIndex(a, &key, &value);
          m_gamerules.push_back(value);
        }
      }
			else if (!stricmp(name, "Minimap"))
			{
				foundMinimapInfo = true;

				const char* minimap_dds = "";
				foundMinimapInfo &= child->getAttr( "Filename", &minimap_dds );
				m_minimapInfo.sMinimapName = GetPath();
				m_minimapInfo.sMinimapName.append("/");
				m_minimapInfo.sMinimapName.append(minimap_dds);

				foundMinimapInfo &= child->getAttr( "startX", m_minimapInfo.fStartX );
				foundMinimapInfo &= child->getAttr( "startY", m_minimapInfo.fStartY );
				foundMinimapInfo &= child->getAttr( "endX", m_minimapInfo.fEndX );
				foundMinimapInfo &= child->getAttr( "endY", m_minimapInfo.fEndY );
				foundMinimapInfo &= child->getAttr( "width", m_minimapInfo.iWidth );
				foundMinimapInfo &= child->getAttr( "height", m_minimapInfo.iHeight );
				m_minimapInfo.fDimX = m_minimapInfo.fEndX - m_minimapInfo.fStartX;
				m_minimapInfo.fDimY = m_minimapInfo.fEndY - m_minimapInfo.fStartY;
				m_minimapInfo.fDimX = m_minimapInfo.fDimX > 0 ? m_minimapInfo.fDimX : 1;
				m_minimapInfo.fDimY = m_minimapInfo.fDimY > 0 ? m_minimapInfo.fDimY : 1;
			}
			else if (!stricmp(name, "Attributes"))
			{
				for(int a = 0; a < child->getChildCount(); ++a)
				{
					XmlNodeRef attrib = child->getChild(a);
					assert(m_levelAttributes.find(attrib->getTag()) == m_levelAttributes.end());
					m_levelAttributes[attrib->getTag()] = TFlowInputData( string(attrib->getAttr("value")) );;
				}
			}
    }
		m_bMetaDataRead = true;
  }
	if (!foundMinimapInfo)
	{
		gEnv->pLog->LogWarning("Map %s: Missing or invalid minimap info!", mapName.c_str());
	}
}

//------------------------------------------------------------------------
const ILevelInfo::TGameTypeInfo *CLevelInfo::GetDefaultGameType() const
{
	if (!m_gameTypes.empty())
	{
		return &m_gameTypes[0];
	}

	return 0;
};

/// Used by console auto completion.
struct SLevelNameAutoComplete : public IConsoleArgumentAutoComplete
{
	std::vector<string> levels;
	virtual int GetCount() const { return levels.size(); };
	virtual const char* GetValue( int nIndex ) const { return levels[nIndex].c_str(); };
};
// definition and declaration must be separated for devirtualization
SLevelNameAutoComplete g_LevelNameAutoComplete;

//------------------------------------------------------------------------
CLevelSystem::CLevelSystem(ISystem *pSystem, const char *levelsFolder)
: m_pSystem(pSystem),
	m_pCurrentLevel(0),
	m_pLoadingLevelInfo(0)
{
	CRY_ASSERT(pSystem);

	//if (!gEnv->IsEditor())
		Rescan(levelsFolder);

	// register with system to get loading progress events
	m_pSystem->SetLoadingProgressListener(this);
	m_fLastLevelLoadTime = 0;
	m_fFilteredProgress = 0;
	m_fLastTime = 0;
	m_bLevelLoaded = false;
	m_bRecordingFileOpens = false;

	m_levelLoadStartTime.SetValue(0);

	m_nLoadedLevelsCount = 0;

	m_extLevelRotations.resize(0);

	gEnv->pConsole->RegisterAutoComplete( "map",&g_LevelNameAutoComplete );
}

//------------------------------------------------------------------------
CLevelSystem::~CLevelSystem()
{
	// register with system to get loading progress events
	m_pSystem->SetLoadingProgressListener(0);
}

//------------------------------------------------------------------------
void CLevelSystem::Rescan(const char *levelsFolder)
{
	if (levelsFolder)
	{
		if (const ICmdLineArg *pModArg = m_pSystem->GetICmdLine()->FindArg(eCLAT_Pre,"MOD"))
		{
			if (m_pSystem->IsMODValid(pModArg->GetValue()))
			{
				m_levelsFolder.Format("Mods/%s/%s/%s", pModArg->GetValue(), PathUtil::GetGameFolder().c_str(), levelsFolder);
				ScanFolder(0, true);
			}
		}

		m_levelsFolder = levelsFolder;
	}

	CRY_ASSERT(!m_levelsFolder.empty());

	m_levelInfos.reserve(64);
	ScanFolder(0, false);

	g_LevelNameAutoComplete.levels.clear();
	for (int i = 0; i < (int)m_levelInfos.size(); i++)
	{
		g_LevelNameAutoComplete.levels.push_back( PathUtil::GetFileName(m_levelInfos[i].GetName()) );
	}
}

void CLevelSystem::LoadRotation()
{
  if (ICVar *pLevelRotation=gEnv->pConsole->GetCVar("sv_levelrotation"))
  {
    ILevelRotationFile* file = 0;
    IPlayerProfileManager *pProfileMan = CCryAction::GetCryAction()->GetIPlayerProfileManager();
    if(pProfileMan)
    {
      const char *userName = pProfileMan->GetCurrentUser();
      IPlayerProfile *pProfile = pProfileMan->GetCurrentProfile(userName);
      if(pProfile)
      {
        file = pProfile->GetLevelRotationFile(pLevelRotation->GetString());
      }
      else if (pProfile = pProfileMan->GetDefaultProfile())
      {
        file = pProfile->GetLevelRotationFile(pLevelRotation->GetString());
      }
    }
    bool ok = false;
    if(file)
    {
      ok = m_levelRotation.Load(file);
      file->Complete();
    }

    if(!ok)
      CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, "Failed to load '%s' as level rotation!", pLevelRotation->GetString());
  }
}

//------------------------------------------------------------------------
void CLevelSystem::ScanFolder(const char *subfolder, bool modFolder)
{
	string folder;
	if (subfolder && subfolder[0])
		folder = subfolder;

	string search(m_levelsFolder);
	if (!folder.empty())
		search += string("/") + folder;
	search += "/*.*";

	ICryPak *pPak = gEnv->pCryPak;

	_finddata_t fd;
	intptr_t handle = 0;

	// --kenzo
	// allow this find first to actually touch the file system
	// (causes small overhead but with minimal amount of levels this should only be around 150ms on actual DVD Emu)
	handle = pPak->FindFirst(search.c_str(), &fd, 0, true);

	if (handle > -1)
	{
		do
		{
			if (!(fd.attrib & _A_SUBDIR) || !strcmp(fd.name, ".") || !strcmp(fd.name, ".."))
			{
				continue;
			}

			CLevelInfo levelInfo;

			string levelFolder = (folder.empty() ? "" : (folder + "/")) + string(fd.name);
			string levelPath = m_levelsFolder + "/" + levelFolder;
			string paks = levelPath + string("/*.pak");

			if (!pPak->IsFileExist( levelPath + "/level.pak", ICryPak::eFileLocation_OnDisk) &&
					!pPak->IsFileExist( levelPath + "/levelinfo.xml"))
			{
				ScanFolder(levelFolder.c_str(), modFolder);
				continue;
			}

			levelInfo.m_levelPath = levelPath;
			levelInfo.m_levelPaks = paks;
			levelInfo.m_levelName = levelFolder;
			levelInfo.m_levelName = UnifyName(levelInfo.m_levelName);
			levelInfo.m_isModLevel = modFolder;

			CLevelInfo *pExistingInfo = GetLevelInfoInternal(levelInfo.m_levelName);
			if( pExistingInfo && pExistingInfo->MetadataLoaded() == false )
			{
				//Reload metadata if it failed to load
				pExistingInfo->ReadMetaData();
			}

			// Don't add the level if it is already in the list
			if ( pExistingInfo == NULL )
			{
				levelInfo.ReadMetaData();

				m_levelInfos.push_back(levelInfo);
			}

		} while (pPak->FindNext(handle, &fd) >= 0);

		pPak->FindClose(handle);
	}
}

//------------------------------------------------------------------------
int CLevelSystem::GetLevelCount()
{
	return (int)m_levelInfos.size();
}

//------------------------------------------------------------------------
ILevelInfo *CLevelSystem::GetLevelInfo(int level)
{
	return GetLevelInfoInternal(level);
}

//------------------------------------------------------------------------
CLevelInfo *CLevelSystem::GetLevelInfoInternal(int level)
{
	if ((level >= 0) && (level < GetLevelCount()))
	{
		return &m_levelInfos[level];
	}

	return 0;
}

//------------------------------------------------------------------------
ILevelInfo *CLevelSystem::GetLevelInfo(const char *levelName)
{
	return GetLevelInfoInternal(levelName);
}

//------------------------------------------------------------------------
CLevelInfo *CLevelSystem::GetLevelInfoInternal(const char *levelName)
{
	// If level not found by full name try comparing with only filename
	for (std::vector<CLevelInfo>::iterator it = m_levelInfos.begin(); it != m_levelInfos.end(); ++it)
	{
		if (!strcmpi(it->GetName(), levelName))
		{
			return &(*it);
		}
	}

	//////////////////////////////////////////////////////////////////////////
	for (std::vector<CLevelInfo>::iterator it = m_levelInfos.begin(); it != m_levelInfos.end(); ++it)
	{
		if (!strcmpi(PathUtil::GetFileName(it->GetName()), levelName))
		{
			return &(*it);
		}
	}

	// Try stripping out the folder to find the raw filename
	string sLevelName(levelName);
	size_t lastSlash = sLevelName.find_last_of('\\');
	if(lastSlash != string::npos)
	{
		sLevelName = sLevelName.substr(lastSlash + 1, sLevelName.size() - lastSlash - 1);
		return GetLevelInfoInternal(sLevelName.c_str());
	}

	return 0;
}

//------------------------------------------------------------------------
void CLevelSystem::AddListener(ILevelSystemListener *pListener)
{
	std::vector<ILevelSystemListener *>::iterator it = std::find(m_listeners.begin(), m_listeners.end(), pListener);

	if (it == m_listeners.end())
	{
		m_listeners.reserve(12);
		m_listeners.push_back(pListener);
	}
}

//------------------------------------------------------------------------
void CLevelSystem::RemoveListener(ILevelSystemListener *pListener)
{
	std::vector<ILevelSystemListener *>::iterator it = std::find(m_listeners.begin(), m_listeners.end(), pListener);

	if (it != m_listeners.end())
	{
		m_listeners.erase(it);

		if (m_listeners.empty())
		{
			stl::free_container(m_listeners);
		}
	}
}

//------------------------------------------------------------------------
ILevel *CLevelSystem::LoadLevel(const char *levelName)
{
	MEMSTAT_CONTEXT_FMT(EMemStatContextTypes::MSC_Other, 0, "Level load (%s)", levelName);
#ifdef PS3
	if(gPS3Env->bForce10HzFlip==1 && gEnv->pRenderer)
	{
		gEnv->pRenderer->ForceSwapBuffers();		
	}
#endif
	// Not remove a scope!!!
	{
		LOADING_TIME_PROFILE_SECTION(GetISystem());
		CRY_ASSERT(levelName);

		CCryAction::GetCryAction()->PauseGame(false, true);

		m_levelLoadStartTime = gEnv->pTimer->GetAsyncTime();

		CLevelInfo *pLevelInfo = GetLevelInfoInternal(levelName);

		if (!pLevelInfo)
		{
			// alert the listener
			OnLevelNotFound(levelName);

			return 0;
		}

		m_bLevelLoaded = false;

		const bool bLoadingSameLevel = m_lastLevelName.compareNoCase(levelName) == 0;
		m_lastLevelName = levelName;

		delete m_pCurrentLevel;
		CLevel *pLevel = new CLevel();
		pLevel->m_levelInfo = *pLevelInfo;
		m_pCurrentLevel = pLevel;

		//////////////////////////////////////////////////////////////////////////
		// Read main level info.
		if (!pLevelInfo->ReadInfo())
		{
			OnLoadingError(pLevelInfo, "Failed to read level info (level.pak might be corrupted)!");
			return 0;
		}
		//[AlexMcC|19.04.10]: Update the level's LevelInfo
		pLevel->m_levelInfo = *pLevelInfo;
		//////////////////////////////////////////////////////////////////////////

		gEnv->pConsole->SetScrollMax(600);
		ICVar *con_showonload = gEnv->pConsole->GetCVar("con_showonload");
		if (con_showonload && con_showonload->GetIVal() != 0)
		{
			gEnv->pConsole->ShowConsole(true);
			ICVar *g_enableloadingscreen = gEnv->pConsole->GetCVar("g_enableloadingscreen");
			if (g_enableloadingscreen)
				g_enableloadingscreen->Set(0);
		}

		// Reset the camera to (0,0,0) which is the invalid/uninitialised state
		CCamera defaultCam;
		m_pSystem->SetViewCamera(defaultCam);

		m_pLoadingLevelInfo = pLevelInfo;
		OnLoadingStart(pLevelInfo);

		// ensure a physical global area is present
		IPhysicalWorld *pPhysicalWorld = gEnv->pPhysicalWorld;
		pPhysicalWorld->AddGlobalArea();

		ICryPak *pPak = gEnv->pCryPak;

		string levelPath = pLevelInfo->GetPath();
		string levelpak = levelPath + string("/level.pak");

		/*
		ICVar *pFileCache = gEnv->pConsole->GetCVar("sys_FileCache");		CRY_ASSERT(pFileCache);

		if(pFileCache->GetIVal())
		{
		if(pPak->OpenPack("",pLevelInfo->GetPath()+string("/FileCache.dat")))
		gEnv->pLog->Log("FileCache.dat loaded");
		else
		gEnv->pLog->Log("FileCache.dat not loaded");
		}
		*/

		m_pSystem->SetThreadState(ESubsys_Physics, false);

		ICVar *pSpamDelay = gEnv->pConsole->GetCVar("log_SpamDelay");
		float spamDelay = 0.0f;
		if (pSpamDelay)
		{
			spamDelay = pSpamDelay->GetFVal();
			pSpamDelay->Set(0.0f);
		}

		IGameTokenSystem* pGameTokenSystem = CCryAction::GetCryAction()->GetIGameTokenSystem();

		// load all GameToken libraries this level uses incl. LevelLocal
		pGameTokenSystem->Reset();
		pGameTokenSystem->LoadLibs( pLevelInfo->GetPath() + string("/GameTokens/*.xml"));

		LoadLevelEntityPrototypes(pLevelInfo);

		if (!gEnv->p3DEngine->LoadLevel(pLevelInfo->GetPath(), pLevelInfo->GetDefaultGameType()->name))
		{
			OnLoadingError(pLevelInfo, "3DEngine failed to handle loading the level");

#if defined(WIN32)
			if(!gEnv->IsEditor())
			{
				CryMessageBox("Level loading failed. Launcher will exit. Try re-exporting the level from the Editor", "Level loading failed", 16);
				exit(0);
			}
#endif
			
			return 0;
		}
		if (!gEnv->pEntitySystem || !gEnv->pEntitySystem->OnLoadLevel(pLevelInfo->GetPath()))
		{
			OnLoadingError(pLevelInfo, "EntitySystem failed to handle loading the level");

			return 0;
		}

		// reset all the script timers
		gEnv->pScriptSystem->ResetTimers();

		if (gEnv->pAISystem)
		{
			gEnv->pAISystem->Reset(IAISystem::RESET_LOAD_LEVEL);
		}

		// Reset TimeOfDayScheduler
		CCryAction::GetCryAction()->GetTimeOfDayScheduler()->Reset();
		CCryAction::GetCryAction()->OnActionEvent(SActionEvent(eAE_loadLevel));

		CCryAction::GetCryAction()->CreatePhysicsQueues();

		// Reset dialog system
		if (gEnv->pDialogSystem)
		{
			gEnv->pDialogSystem->Reset(false);
			gEnv->pDialogSystem->Init();
		}

		// Reset sound system.
		gEnv->pSoundSystem->Silence(true, false);
		gEnv->pSoundSystem->GetInterfaceExtended()->RecomputeSoundOcclusion(true,true,true);
		gEnv->pSoundSystem->Update(eSoundUpdateMode_All); // update once so sounds actually stop
		gEnv->pSoundSystem->Reset(false);

		// Make sure to remove possibly cached music files beforehand and then cache the files associated with the level to load.
		gEnv->pSoundSystem->ClearAudioDataCache(eAFCT_MUSIC);
		gEnv->pSoundSystem->CacheAudioData(levelName);

		if (gEnv->pAISystem && gEnv->pAISystem->IsEnabled())
		{
			gEnv->pAISystem->FlushSystem();
			gEnv->pAISystem->LoadLevelData(pLevelInfo->GetPath(), pLevelInfo->GetDefaultGameType()->name);
		}

		const ILevelInfo::TStringVec& musicLibs = pLevelInfo->GetMusicLibs();
		for (ILevelInfo::TStringVec::const_iterator i = musicLibs.begin(); i!= musicLibs.end(); ++i)
		{
			gEnv->pMusicSystem->LoadFromXML(*i, true, false);
		}

		if (gEnv->pEntitySystem)
		{
			gEnv->pEntitySystem->ReserveEntityId(1);
			gEnv->pEntitySystem->ReserveEntityId(LOCAL_PLAYER_ENTITY_ID);
		}

		CCryAction::GetCryAction()->GetIGameRulesSystem()->CreateGameRules(CCryAction::GetCryAction()->GetGameContext()->GetRequestedGameRules());

		string missionXml = pLevelInfo->GetDefaultGameType()->xmlFile;
		string xmlFile = string(pLevelInfo->GetPath()) + "/" + missionXml;

		XmlNodeRef rootNode = m_pSystem->LoadXmlFromFile(xmlFile.c_str());
		if (rootNode)
		{
			const char *script = rootNode->getAttr("Script");

			if (script && script[0])
				gEnv->pScriptSystem->ExecuteFile(script, true, true);

			XmlNodeRef objectsNode = rootNode->findChild("Objects");

			if (objectsNode)
			{
				// Stop the network ticker thread before loading entities - otherwise the network queues
				// can get polled mid way through spawning an entity resulting in tasks being handled in
				// the wrong order.
				// Note: The network gets ticked after every 8 entity spawns so we are still ticking, just
				// not from the ticker thread.
				SCOPED_TICKER_LOCK;

				gEnv->pEntitySystem->LoadEntities(objectsNode);
			}
		}

		// Now that we've registered our AI objects, we can init
		if (gEnv->pAISystem)
			gEnv->pAISystem->Reset(IAISystem::RESET_ENTER_GAME);

		//////////////////////////////////////////////////////////////////////////
		// Movie system must be loaded after entities.
		//////////////////////////////////////////////////////////////////////////
		string movieXml = pLevelInfo->GetPath() + string("/moviedata.xml");
		IMovieSystem *movieSys = gEnv->pMovieSystem;
		if (movieSys != NULL)
		{
			movieSys->Load( movieXml, pLevelInfo->GetDefaultGameType()->name );
			movieSys->Reset(true,false); // bSeekAllToStart needs to be false here as it's only of interest in the editor (double checked with Timur Davidenko)
		}

		if (IMaterialEffects* pMaterialEffects = CCryAction::GetCryAction()->GetIMaterialEffects())
		{
			pMaterialEffects->PreLoadAssets();
		}
		
		{
			LOADING_TIME_PROFILE_SECTION_NAMED("ENTITY_EVENT_LEVEL_LOADED");

			//////////////////////////////////////////////////////////////////////////
			SEntityEvent loadingCompleteEvent(ENTITY_EVENT_LEVEL_LOADED);
			gEnv->pEntitySystem->SendEventToAll( loadingCompleteEvent );
			//////////////////////////////////////////////////////////////////////////
		}

		gEnv->pFlowSystem->Reset(false);

		{
			LOADING_TIME_PROFILE_SECTION_NAMED("OnLoadingComplete");
			// Inform Level system listeners that loading of the level is complete.
			for (std::vector<ILevelSystemListener *>::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it)
			{
				(*it)->OnLoadingComplete(m_pCurrentLevel);
			}
		}

		// Let gamerules precache anything needed
		if (IGameRules *pGameRules=CCryAction::GetCryAction()->GetIGameRulesSystem()->GetCurrentGameRules())
		{
			pGameRules->PrecacheLevel();
		}

		//////////////////////////////////////////////////////////////////////////
		// Notify 3D engine that loading finished
		//////////////////////////////////////////////////////////////////////////
		gEnv->p3DEngine->PostLoadLevel();

		if (gEnv->pScriptSystem)
		{
			// After level was loaded force GC cycle in Lua
			gEnv->pScriptSystem->ForceGarbageCollection();
		}
		//////////////////////////////////////////////////////////////////////////

		//////////////////////////////////////////////////////////////////////////
		//////////////////////////////////////////////////////////////////////////
		gEnv->pConsole->SetScrollMax(600/2);

		pPak->GetResourceList(ICryPak::RFOM_NextLevel)->Clear();

		if (pSpamDelay)
			pSpamDelay->Set(spamDelay);

#if CAPTURE_REPLAY_LOG
		CryGetIMemReplay()->AddLabelFmt("loadEnd%d_%s", s_loadCount++, levelName);
#endif

		m_bLevelLoaded = true;
	}

	GetISystem()->GetISystemEventDispatcher()->OnSystemEvent( ESYSTEM_EVENT_LEVEL_LOAD_END,0,0 );

	m_pSystem->SetThreadState(ESubsys_Physics, true);

	gEnv->pConsole->GetCVar("sv_map")->Set(levelName);

	gEnv->pCryPak->TouchDummyFile("endlevelload");

  gEnv->pSystem->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_PRECACHE_START, NULL, NULL);

	if (!gEnv->pSystem->IsSerializingFile() && gEnv->pEntitySystem)
	{
		// activate the default layers
		gEnv->pEntitySystem->EnableDefaultLayers();
	}

	return m_pCurrentLevel;
}

//------------------------------------------------------------------------
ILevel* CLevelSystem::SetEditorLoadedLevel(const char *levelName)
{
	CLevelInfo *pLevelInfo = GetLevelInfoInternal(levelName);

	if (!pLevelInfo)
	{
		// alert the listener
		OnLevelNotFound(levelName);

		return 0;
	}

	m_lastLevelName = levelName;

	//////////////////////////////////////////////////////////////////////////
	// Read main level info.
	pLevelInfo->ReadInfo();
	pLevelInfo->ReadMetaData();
// 	if (!pLevelInfo->ReadInfo())
// 	{
// 		OnLoadingError(pLevelInfo, "Failed to read level info (level.pak might be corrupted)!");
// 		return 0;
// 	}
	//////////////////////////////////////////////////////////////////////////

	delete m_pCurrentLevel;
	CLevel *pLevel = new CLevel();
	pLevel->m_levelInfo = *pLevelInfo;
	m_pCurrentLevel = pLevel;

	m_bLevelLoaded = true;

	// Let gamerules precache anything needed
	if (IGameRules *pGameRules=CCryAction::GetCryAction()->GetIGameRulesSystem()->GetCurrentGameRules())
	{
		pGameRules->PrecacheLevel();
	}

	return m_pCurrentLevel;
}

//------------------------------------------------------------------------
// Search positions of all entities with class "PrecacheCamera" and pass it to the 3dengine
void CLevelSystem::PrecacheLevelRenderData()
{
	if (gEnv->IsDedicated())
		return;

//	gEnv->pSystem->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_PRECACHE_START, NULL, NULL);
#ifndef CONSOLE_CONST_CVAR_MODE
	ICVar *pPrecacheVar = gEnv->pConsole->GetCVar("e_PrecacheLevel");		CRY_ASSERT(pPrecacheVar);

	if(pPrecacheVar && pPrecacheVar->GetIVal()> 0)
	{

		if (gEnv->pGame)
			if(I3DEngine * p3DEngine = gEnv->p3DEngine)
			{
				std::vector<Vec3> arrCamOutdoorPositions;

				IEntitySystem *pEntitySystem = gEnv->pEntitySystem;
				IEntityItPtr pEntityIter = pEntitySystem->GetEntityIterator();

				IEntityClass *pPrecacheCameraClass = pEntitySystem->GetClassRegistry()->FindClass( "PrecacheCamera" );
				IEntity *pEntity = NULL;
				while (pEntity = pEntityIter->Next())
				{
					if (pEntity->GetClass() == pPrecacheCameraClass)
					{
						arrCamOutdoorPositions.push_back( pEntity->GetWorldPos() );
					}
				}
				Vec3 *pPoints = 0;
				if (arrCamOutdoorPositions.size() > 0)
				{
					pPoints = &arrCamOutdoorPositions[0];
				}

				p3DEngine->PrecacheLevel(true, pPoints,arrCamOutdoorPositions.size() );
			}
	}
#endif
//	gEnv->pSystem->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_PRECACHE_END, NULL, NULL);
}

//------------------------------------------------------------------------
void CLevelSystem::PrepareNextLevel(const char *levelName)
{
#ifdef PS3
	if(gEnv->pRenderer && gPS3Env->bForce10HzFlip == 2)
	{
		gEnv->pRenderer->ForceSwapBuffers();
	}
#endif
	m_levelLoadStartTime = gEnv->pTimer->GetAsyncTime();
	CLevelInfo *pLevelInfo = GetLevelInfoInternal(levelName);
	if (!pLevelInfo)
	{
		// alert the listener
		//OnLevelNotFound(levelName);
		return;
	}

	// force a Lua deep garbage collection 
	{
		gEnv->pScriptSystem->ForceGarbageCollection();
	}

	// swap to the level heap
	CCryAction::GetCryAction()->SwitchToLevelHeap(levelName);

	// switched to level heap, so now imm start the loading screen (renderer will be reinitialized in the levelheap)
	gEnv->pSystem->GetISystemEventDispatcher()->OnSystemEvent( ESYSTEM_EVENT_LEVEL_LOAD_START_LOADINGSCREEN, (UINT_PTR)pLevelInfo, 0 );

	// Open pak file for a new level.
	pLevelInfo->OpenLevelPak();

	// Inform resource manager about loading of the new level.
	GetISystem()->GetIResourceManager()->PrepareLevel( pLevelInfo->GetPath(),pLevelInfo->GetName() );

	// Disable locking of resources to allow everything to be offloaded.

	//string filename = PathUtil::Make( pLevelInfo->GetPath(),"resourcelist.txt" );
	//gEnv->pCryPak->GetResourceList(ICryPak::RFOM_NextLevel)->Load( filename.c_str() );

#ifdef PS3
	if(gEnv->pRenderer && gPS3Env->bForce10HzFlip == 1)
	{
		gEnv->pRenderer->ForceSwapBuffers();
	}
#endif
}

//------------------------------------------------------------------------
void CLevelSystem::OnLevelNotFound(const char *levelName)
{
	for (std::vector<ILevelSystemListener *>::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it)
	{
		(*it)->OnLevelNotFound(levelName);
	}
}

//------------------------------------------------------------------------
void CLevelSystem::OnLoadingStart(ILevelInfo *pLevelInfo)
{
	m_fFilteredProgress = 0.f;
	m_fLastTime = gEnv->pTimer->GetAsyncCurTime();

  GetISystem()->GetISystemEventDispatcher()->OnSystemEvent( ESYSTEM_EVENT_LEVEL_LOAD_START,0,0 );

	LOADING_TIME_PROFILE_SECTION(gEnv->pSystem);

#ifdef WIN32
	/*
	m_bRecordingFileOpens = GetISystem()->IsDevMode() && gEnv->pCryPak->GetRecordFileOpenList() == ICryPak::RFOM_Disabled;
	if (m_bRecordingFileOpens)
	{
		gEnv->pCryPak->GetResourceList(ICryPak::RFOM_Level)->Clear();
		gEnv->pCryPak->RecordFileOpen(ICryPak::RFOM_Level);
	}
	*/
#endif

	for (std::vector<ILevelSystemListener *>::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it)
	{
		(*it)->OnLoadingStart(pLevelInfo);
	}

	BEGIN_LOADING(pLevelInfo->GetName());
}

//------------------------------------------------------------------------
void CLevelSystem::OnLoadingError(ILevelInfo *pLevelInfo, const char *error)
{
	if (!pLevelInfo)
		pLevelInfo = m_pLoadingLevelInfo;
	if (!pLevelInfo)
	{
		CRY_ASSERT(false);
		GameWarning("OnLoadingError without a currently loading level");
	}

	if(gEnv->pRenderer)
	{
		gEnv->pRenderer->SetTexturePrecaching(false);
	}

	for (std::vector<ILevelSystemListener *>::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it)
	{
		(*it)->OnLoadingError(pLevelInfo, error);
	}

	END_LOADING(pLevelInfo->GetName());

	((CLevelInfo*)pLevelInfo)->CloseLevelPak();
}

//------------------------------------------------------------------------
void CLevelSystem::OnLoadingComplete(ILevel *pLevelInfo)
{
  PrecacheLevelRenderData();
	
	if (m_bRecordingFileOpens)
	{
		// Stop recoding file opens.
		gEnv->pCryPak->RecordFileOpen(ICryPak::RFOM_Disabled);
		// Save recorded list.
		SaveOpenedFilesList();
	}

	CTimeValue t = gEnv->pTimer->GetAsyncTime();
	m_fLastLevelLoadTime = (t-m_levelLoadStartTime).GetSeconds();
  
	if (!gEnv->IsEditor())
	{
		CryLog( "-----------------------------------------------------" );
		CryLog( "*LOADING: Level %s loading time: %.2f seconds",m_lastLevelName.c_str(),m_fLastLevelLoadTime );
		CryLog( "-----------------------------------------------------" );
	}

	LogLoadingTime();

	m_nLoadedLevelsCount++;

	// Hide console after loading.
	gEnv->pConsole->ShowConsole(false);

//  gEnv->pCryPak->GetFileReadSequencer()->EndSection();

	if (!gEnv->bServer)
		return;

	if (gEnv->pCharacterManager)
	{
		SAnimMemoryTracker amt; 
		gEnv->pCharacterManager->SetAnimMemoryTracker(amt);
	}
	/*
	if( IStatsTracker* tr = CCryAction::GetCryAction()->GetIGameStatistics()->GetSessionTracker() )
	{
		string mapName = "no_map_assigned";
		if( pLevelInfo )
			if (ILevelInfo * pInfo = pLevelInfo->GetLevelInfo())
			{
				mapName = pInfo->GetName();
				PathUtil::RemoveExtension(mapName);
				mapName = PathUtil::GetFileName(mapName);
				mapName.MakeLower();
			}
		tr->StateValue(eSP_Map, mapName.c_str());
	}
	*/

	// LoadLevel is not called in the editor, hence OnLoadingComplete is not invoked on the ILevelSystemListeners
	if (gEnv->IsEditor())
	{
		for (std::vector<ILevelSystemListener *>::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it)
		{
			(*it)->OnLoadingComplete(pLevelInfo);
		}

    SEntityEvent loadingCompleteEvent(ENTITY_EVENT_LEVEL_LOADED);
    gEnv->pEntitySystem->SendEventToAll( loadingCompleteEvent );
	}

	END_LOADING(pLevelInfo->GetLevelInfo()->GetName());
}

//------------------------------------------------------------------------
void CLevelSystem::OnLoadingProgress(ILevelInfo *pLevel, int progressAmount)
{
	for (std::vector<ILevelSystemListener *>::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it)
	{
		(*it)->OnLoadingProgress(pLevel, progressAmount);
	}
}

//------------------------------------------------------------------------
void CLevelSystem::OnUnloadComplete(ILevel* pLevel)
{
	for (std::vector<ILevelSystemListener *>::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it)
	{
		(*it)->OnUnloadComplete(pLevel);
	}
}

//------------------------------------------------------------------------
void CLevelSystem::OnLoadingProgress(int steps) 
{
	float fProgress = (float)gEnv->p3DEngine->GetLoadedObjectCount();

  m_fFilteredProgress = min(m_fFilteredProgress, fProgress);

  float fFrameTime = gEnv->pTimer->GetAsyncCurTime() - m_fLastTime;

  float t = CLAMP(fFrameTime*.25f, 0.0001f, 1.0f);

  m_fFilteredProgress = fProgress*t + m_fFilteredProgress*(1.f-t);

  m_fLastTime = gEnv->pTimer->GetAsyncCurTime();

	OnLoadingProgress( m_pLoadingLevelInfo, (int)m_fFilteredProgress );
}

//------------------------------------------------------------------------
string& CLevelSystem::UnifyName(string& name)
{
	//name.MakeLower();
	name.replace('\\', '/');

	return name;
}

//////////////////////////////////////////////////////////////////////////
void CLevelSystem::LogLoadingTime()
{
	if (gEnv->IsEditor())
		return;

	if (!GetISystem()->IsDevMode())
		return;

#if defined(WIN32) && !defined(XENON)		
	CDebugAllowFileAccess ignoreInvalidFileAccess;

	string filename = gEnv->pSystem->GetRootFolder();
	filename += "Game_LevelLoadTime.log";

	FILE *file = fxopen(filename,"at");
	if (!file)
		return;

	char vers[128];
	GetISystem()->GetFileVersion().ToString(vers);

	const char *sChain = "";
	if (m_nLoadedLevelsCount > 0)
		sChain = " (Chained)";

	string text;
	text.Format( "\n[%s] Level %s loaded in %d seconds%s",vers,m_lastLevelName.c_str(),(int)m_fLastLevelLoadTime,sChain );
	fwrite( text.c_str(),text.length(),1,file );
	fclose(file);

#endif // Only log on windows.
}

void CLevelSystem::GetMemoryUsage(ICrySizer * pSizer) const
{
	pSizer->AddObject(this, sizeof(*this));
	pSizer->AddObject(m_levelInfos);	
	pSizer->AddObject(m_levelsFolder);
	pSizer->AddObject(m_listeners);		
}

void CLevelInfo::GetMemoryUsage(ICrySizer * pSizer) const
{
	pSizer->AddObject(m_levelName);
	pSizer->AddObject(m_levelPath);
	pSizer->AddObject(m_levelPaks);
	pSizer->AddObject(m_musicLibs);	
	pSizer->AddObject(m_gamerules);	
	pSizer->AddObject(m_gameTypes);	
}

//////////////////////////////////////////////////////////////////////////
bool CLevelInfo::OpenLevelPak()
{
	string levelpak = m_levelPath + string("/level.pak");
	if (!gEnv->pCryPak->OpenPack( levelpak,(unsigned)0 ))
	{
		return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
void CLevelInfo::CloseLevelPak()
{
	string levelpaks = m_levelPath + string("/*.pak");
	gEnv->pCryPak->ClosePacks(levelpaks,  (unsigned int)0);
}

//////////////////////////////////////////////////////////////////////////
void CLevelSystem::SaveOpenedFilesList()
{
	if (!m_pLoadingLevelInfo)
		return;

	// Write resource list to file.
	string filename = PathUtil::Make( m_pLoadingLevelInfo->GetPath(),"resourcelist.txt" );
	FILE *file = fxopen( filename.c_str(),"wt",true );
	if (file)
	{
		IResourceList *pResList = gEnv->pCryPak->GetResourceList(ICryPak::RFOM_Level);
		for (const char *fname = pResList->GetFirst(); fname; fname = pResList->GetNext())
		{
			fprintf( file,"%s\n",fname );
		}
		fclose(file);
	}
}

//////////////////////////////////////////////////////////////////////////
void CLevelSystem::UnLoadLevel()
{
	if (gEnv->IsEditor())
		return;
#ifdef PS3
	bool flipIssued = false;		//enable flipping for PS3 TRC
	if(gEnv->pRenderer)
	{
		gPS3Env->bForce10HzFlip = 0;
		gEnv->pRenderer->ForceSwapBuffers();
		flipIssued = true;
	}
#endif
	CryLog( "UnLoadLevel Start" );

	CTimeValue tBegin = gEnv->pTimer->GetAsyncTime();

	I3DEngine* p3DEngine = gEnv->p3DEngine;
	if ( p3DEngine )
	{
		IDeferredPhysicsEventManager* pPhysEventManager = p3DEngine->GetDeferredPhysicsEventManager();
		if ( pPhysEventManager )
		{
			// clear deferred physics queues before renderer, since we could have spu jobs running
			// which access a rendermesh
			pPhysEventManager->ClearDeferredEvents();
		}
	}

	//AM: Flush render thread (Flush is not exposed - using EndFrame())
	//We are about to delete resources that could be in use
	if (gEnv->pRenderer)
	{
		gEnv->pRenderer->EndFrame();

		// force a black screen as last render command
		gEnv->pRenderer->BeginFrame();
		gEnv->pRenderer->SetState(GS_BLSRC_SRCALPHA|GS_BLDST_ONEMINUSSRCALPHA|GS_NODEPTHTEST);
		gEnv->pRenderer->Draw2dImage(0, 0, 800, 600, -1, 0.0f,0.0f,1.0f,1.0f,	0.f,
			0.0f, 0.0f, 0.0f, 1.0 ,0.f); 
		gEnv->pRenderer->EndFrame();
	}

	// Disable filecaching during level unloading
	// will be reenabled when we get back to the IIS (frontend directly)
	// or after level loading is finished (via system event system)
	gEnv->pSystem->GetPlatformOS()->AllowOpticalDriveUsage(false);

#ifdef XENON
	// suspend the device and when renderer restored resume it to meet TCR: 022
	// this is done in CRenderer::FreeResources; no need to Suspend here since unloading screen will render until FreeResources is called
	//gEnv->pRenderer->SuspendDevice();
#endif

	if (gEnv->pScriptSystem)
	{
		gEnv->pScriptSystem->ResetTimers();
	}

	CCryAction* pCryAction = CCryAction::GetCryAction();
	if (pCryAction)
	{
		IItemSystem* pItemSystem = pCryAction->GetIItemSystem();
		pItemSystem->ClearGeometryCache();
		pItemSystem->ClearSoundCache();
		pItemSystem->Reset();

		if(ICooperativeAnimationManager* pCoopAnimManager = pCryAction->GetICooperativeAnimationManager())
		{
			pCoopAnimManager->Reset();
		}

		pCryAction->ClearBreakHistory();
	}
	
	// reset a bunch of subsystems
	if (gEnv->pDialogSystem)
	{
		gEnv->pDialogSystem->Reset(true);
	}

	IMaterialEffects* pMaterialEffects = (gEnv->pGame && gEnv->pGame->GetIGameFramework()) ? gEnv->pGame->GetIGameFramework()->GetIMaterialEffects() : NULL;
	if (pMaterialEffects)
	{
		pMaterialEffects->Reset(true);
	}

	if (gEnv->pAISystem)
	{
		gEnv->pAISystem->FlushSystem();
		gEnv->pAISystem->Reset(IAISystem::RESET_EXIT_GAME);
		gEnv->pAISystem->Reset(IAISystem::RESET_UNLOAD_LEVEL);
	}	

	if (gEnv->pEntitySystem)
	{
		gEnv->pEntitySystem->Unload(); // This needs to be called for editor and game, otherwise entities won't be released
	}

	if (pCryAction)
	{
		pCryAction->OnActionEvent(SActionEvent(eAE_unloadLevel));
		// clear the physics queues after the entity system is unloaded, every entity which uses the user proxy and an interactor extension relies on it
		pCryAction->ClearPhysicsQueues();
		pCryAction->GetTimeOfDayScheduler()->Reset();
	}

	if (gEnv->pMovieSystem)
	{
		gEnv->pMovieSystem->Reset(false,false);
		gEnv->pMovieSystem->RemoveAllSequences();
	}

	if( gEnv->pSoundSystem )
	{
		// disallow readrequest during unload to speed up level unloading
		gEnv->pSoundSystem->SetAllowReadRequests(false);
	}

	if (gEnv->pMusicSystem)
	{
		gEnv->pMusicSystem->Unload();
	}

	// Mute and reset the SoundSystem and unload data.
	// Do this before engine resource get released!
	if (gEnv->pSoundSystem)
	{
		gEnv->pSoundSystem->Reset(true);

		// Remove all cached audio files except the global ones
		gEnv->pSoundSystem->ClearAudioDataCache(eAFCT_LEVEL_SPECIFIC);
		gEnv->pSoundSystem->ClearAudioDataCache(eAFCT_GAME_HINT);
		gEnv->pSoundSystem->ClearAudioDataCache(eAFCT_MUSIC);
		gEnv->pSoundSystem->ClearAudioDataCache(eAFCT_FSB_HEADER);
		gEnv->pSoundSystem->ClearAudioDataCache(eAFCT_NOTCACHED);
	}

	// Delete engine resources	
	if (p3DEngine)
	{
		p3DEngine->UnloadLevel();
	}

	if( gEnv->pSoundSystem )
	{
		gEnv->pSoundSystem->SetAllowReadRequests(true);
	}

	if (pCryAction)
	{
		CAnimationGraphManager* pAGManager = pCryAction->GetAnimationGraphManager();
		if (pAGManager)
		{
			pAGManager->FreeGraphs();
		}

		IGameObjectSystem* pGameObjectSystem = pCryAction->GetIGameObjectSystem();
		pGameObjectSystem->Reset();
	}

	IGameTokenSystem* pGameTokenSystem = CCryAction::GetCryAction()->GetIGameTokenSystem();
	pGameTokenSystem->RemoveLibrary("Level");
	pGameTokenSystem->Unload();

	if (gEnv->pFlowSystem)
	{
		gEnv->pFlowSystem->Reset(true);
	}

	// Reset the camera to (0,0,0) which is the invalid/uninitialised state
	CCamera defaultCam;
	m_pSystem->SetViewCamera(defaultCam);

	if (pCryAction)
	{
		pCryAction->OnActionEvent(SActionEvent(eAE_postUnloadLevel));
	}

	OnUnloadComplete(m_pCurrentLevel);

	// -- kenzo: this will close all pack files for this level 
	// (even the ones which were not added through here, if this is not desired,
	// then change code to close only level.pak)
	if( m_pLoadingLevelInfo )
	{
		((CLevelInfo*)m_pLoadingLevelInfo)->CloseLevelPak();
	}	

	stl::free_container(m_lastLevelName);

	GetISystem()->GetIResourceManager()->UnloadLevel();

	SAFE_RELEASE(m_pCurrentLevel);

	m_bLevelLoaded = false;

	CTimeValue tUnloadTime = gEnv->pTimer->GetAsyncTime() - tBegin;
	CryLogAlways( "UnLoadLevel End: %.1f sec", tUnloadTime.GetSeconds() );

#ifdef PS3
	if(flipIssued && gEnv->pRenderer)
	{
		gEnv->pRenderer->ForceSwapBuffers();	//disable flipping for PS3 TRC
		gPS3Env->bForce10HzFlip = 2;//flag for upcoming level load to reenable
	}
#endif
}

//////////////////////////////////////////////////////////////////////////
ILevelRotation* CLevelSystem::FindLevelRotationForExtInfoId(const ILevelRotation::TExtInfoId findId)
{
	CLevelRotation*  pRot = NULL;

	TExtendedLevelRotations::iterator  begin = m_extLevelRotations.begin();
	TExtendedLevelRotations::iterator  end = m_extLevelRotations.end();
	for (TExtendedLevelRotations::iterator i = begin; i != end; ++i)
	{
		CLevelRotation*  pIterRot = &(*i);
		if (pIterRot->GetExtendedInfoId() == findId)
		{
			pRot = pIterRot;
			break;
		}
	}

	return pRot;
}

//////////////////////////////////////////////////////////////////////////
bool CLevelSystem::AddExtendedLevelRotationFromXmlRootNode(const XmlNodeRef rootNode, const char* altRootTag, const ILevelRotation::TExtInfoId extInfoId)
{
	bool  ok = false;

	CRY_ASSERT(extInfoId);

	if (!FindLevelRotationForExtInfoId(extInfoId))
	{
		m_extLevelRotations.push_back(CLevelRotation());
		CLevelRotation*  pRot = &m_extLevelRotations.back();

		if (pRot->LoadFromXmlRootNode(rootNode, altRootTag))
		{
			pRot->SetExtendedInfoId(extInfoId);
			ok = true;
		}
		else
		{
			LOCAL_WARNING(0, string().Format("Couldn't add extended level rotation with id '%u' because couldn't read the xml info root node for some reason!", extInfoId));
		}
	}
	else
	{
		LOCAL_WARNING(0, string().Format("Couldn't add extended level rotation with id '%u' because there's already one with that id!", extInfoId));
	}

	return ok;
}

//////////////////////////////////////////////////////////////////////////
void CLevelSystem::ClearExtendedLevelRotations()
{
	m_extLevelRotations.clear();
}

//////////////////////////////////////////////////////////////////////////
ILevelRotation * CLevelSystem::CreateNewRotation( const ILevelRotation::TExtInfoId id )
{
	CLevelRotation *pRotation = static_cast<CLevelRotation*>(FindLevelRotationForExtInfoId(id));

	if (!pRotation)
	{
		m_extLevelRotations.push_back(CLevelRotation());
		pRotation = &m_extLevelRotations.back();
		pRotation->SetExtendedInfoId(id);
	}
	else
	{
		pRotation->Reset();
		pRotation->SetExtendedInfoId(id);
	}

	return pRotation;
}

void CLevelSystem::LoadLevelEntityPrototypes( CLevelInfo* pLevelInfo )
{
	string levelEntityArchetypes = PathUtil::Make(pLevelInfo->GetPath(), "LevelEntityPrototypes.xml");
	XmlNodeRef libNode = GetISystem()->LoadXmlFromFile(levelEntityArchetypes.c_str());
	if (libNode)
	{
		for (int i=0; i<libNode->getChildCount(); ++i)
		{
			gEnv->pEntitySystem->LoadEntityArchetype(libNode->getChild(i));
		}
	}
}


#undef LOCAL_WARNING

#include UNIQUE_VIRTUAL_WRAPPER(ILevelSystem)
#include UNIQUE_VIRTUAL_WRAPPER(ILevelInfo)
#include UNIQUE_VIRTUAL_WRAPPER(ILevelRotation)
